/* License Notice:
**
** This program is free software: you can redistribute it and/or modify
**    it under the terms of the GNU General Public License as published by
**    the Free Software Foundation, either version 3 of the License, or
**    (at your option) any later version.
** This program is distributed in the hope that it will be useful,
**   but WITHOUT ANY WARRANTY; without even the implied warranty of
**   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
**   GNU General Public License for more details.
** You should have received a copy of the GNU General Public License
**   along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

/**
 * @file event_manager.hpp
 * @author TooOld2Rock'nRoll
 * @date 2023/08/10
 * @brief Implements the framework for dealing with system events and user input for the ArcadeFighter library.
 *
 * @bug Possibly, the time for key HOLD events may cause problems with the other events (that get time stamp from SLD). We will know for sure when creating moves esqueces for the characters.
 * @bug Possibly, waitKeyUpEvent() is now broken since we started filtering events.
 * @todo Add a method to just clear all events in the queue for a specific listener.
 * @todo Must find some policy to clear our events queues if some events are not being consumed, old events get kicked? How old?
 * @todo I still can't use proper templates in the event_s structure.... it would make things much easier and avoid two crap methods necessary to using void*, the consequence is always the same, to use dynamic_cast in every event comparison!!!
 * @todo Create our own list of "system" events. System event types are: https://wiki.libsdl.org/SDL2/SDL_EventType make a list of interesting.
 * @todo add Mouse events
 * @todo add controller events
 */
#ifndef _EVENT_MANAGER_HPP_
#define _EVENT_MANAGER_HPP_

/*---- Includes ----*/
#include <functional>
#include <atomic>
#include <mutex>

#include <glm/glm.hpp>
#include <SDL2/SDL_video.h>
#include <SDL2/SDL_events.h>

#include "toolbox/fifo_simple.hpp"
#include "toolbox/linked_list_fast.hpp"
#include "input_devices/keyboard.hpp"
#include "input_devices/controller.hpp"


/*---- Typedefs ----*/
/** @brief A more elegant name to keep track of EventManager's registered listeners. */
typedef unsigned listenerID_t;


/*---- Enumerations ----*/
extern "C" {
/**
 * @brief These are the minimum expected events to be triggered wile a game is running.
 *
 * We made it a C style enumeration (no strong type), so you can extend it freely or overwrite it completely and
 *    still be able to override this class virtual methods. Remember, enums in C are just constant integers!<br>
 * You may do so by something like the following example and when you use push() you just pass original or
 *    extended values interchangeably:
 * @code
 *    typedef enum _extended_game_event_id_
 *    {
 *        MY_FIRST_EVENT = game_event_id_t::_game_event_id_max, //<-- your enumeration will start where this ended
 *        MY_SECOND_EVENT,
 *        ...
 *        _extend__game_event_id_max
 *    } extended_game_event_id_e;
 * @endcode
 */
typedef enum _game_event_id_
{
    EV_LEVEL_STARTED = 0,   ///<Signals that a level has been started.
    EV_LEVEL_ENDED,         ///<Signals that the running level has ended.

    _game_event_id_max      ///<Keeps track of the enum size
} game_event_id_e;//end game_event_id
}//end extern "C"


/*---- Structures ----*/
//keep these structures as simple structs, no methods!! (modern c++ bullshit....)

/** @brief Detailed information for Window events.  */
struct window_event_info_s
{
    ///These will be valid for Window type events. Valid values are: https://wiki.libsdl.org/SDL2/SDL_WindowEventID
    SDL_WindowEventID window_ev_id = SDL_WINDOWEVENT_NONE;

    ///Value changes depending on the window event id.
    int i_data1 = 0;
    ///Value changes depending on the window event id.
    int i_data2 = 0;
};//end window_event_info


/** @brief Detailed information for Keyboard events.  */
struct keyboard_event_info_s
{
    ///In which state the keyboard key is right now?
    key_state state = key_state::_key_state_max;
    ///CTRL, ALT, etc, modifiers. Bit mask of the following valid values: https://wiki.libsdl.org/SDL2/SDL_Keymod
    SDL_Keymod _modifyer = KMOD_NONE;
    ///API known key scan codes: https://wiki.libsdl.org/SDL2/SDL_Scancode
    SDL_Scancode _scancode = SDL_SCANCODE_UNKNOWN;
};//end keyboard


/** @brief Detailed information for Controller events.  */
struct controller_event_info_s
{
    ///In which state the controller key is right now?
    key_state _state = key_state::_key_state_max;
    ///The joystick instance id
    SDL_JoystickID _which_stick_id = -1;
    ///Which of the controller keys are generating the event.
    controller_button_map_t _button = controller_button_map_t::_button_max;
};//end keyboard


/** @brief Detailed information for in game events.  */
struct game_event_info_s
{
    unsigned game_ev_id = game_event_id_e::_game_event_id_max; ///<The event identifier.
    glm::vec3 v3_pos = glm::vec3 (0.f); ///<Where in the world the event happened?
};//end game_event_info


/*---- Class Declaration ----*/
/**
 * @brief A simple Event Manager that sort events and forward to registered listeners.
 *
 * Listening for system events and user input is quite straightforward.<br>
 * The first thing to do is acquire an ID to access the class other methods and than, inform the manager which events you
 *    are interested.<br>
 * The following example adds the Escape Key Release event to the list:
 * \code
 *      listenerID_t myID = 0;
 *
 *      myID = ResourceManager::instance().addListener ();
 *      EventManager.instance().listenForEvent (myID, EventManager::UP, SDL_SCANCODE_ESCAPE);
 * \endcode
 *
 * When ready to read events from the queue, fetch one at a time in a while loop until receiving null, casting the
 *    event info value appropriately depending of the event type informed.<br>
 * Another way of listening for events is using callbacks for real time handling. 
 *
 * @remark Thread safe singleton according to Game Programming Patterns.
 *
 * @see https://gameprogrammingpatterns.com/singleton.html
 * @see https://gameprogrammingpatterns.com/event-queue.html
 */
class EventManager
{
    public:
        /** @brief Defines where the event is coming from and, indirectly, which other fields from the event structure are valid. */
        enum event_type_e
        {
            NONE =          0b00000000, ///<An invalid event.
            QUIT =          0b00000001, ///<System is telling the program to be terminated.
            WINDOW =        0b00000010, ///<It's a window related event, like when it changes size.
            KEYBOARD =      0b00000100, ///<It's a keyboard event.
            MOUSE =         0b00001000, ///<It's a mouse event.
            JOYSTICK =      0b00010000, ///<It's a joystick event.
            CONTROLLER =    0b00100000, ///<It's a controller event.
            GAME =          0b01000000, ///<A in game event.

            /* add other event types as they are needed */
            /* remember to increase/decrease _event_type_max! */

            USER_DEFINED =  0b10000000, ///<Something created by the user.
            _event_type_max = 9 ///<just to keep track of enum size.
        };//end event_type

        /** @brief Event structure with information about what type of event happened and specific data (if any).  */
        struct event_s
        {
            ///The real time the event was processed.
            double d_time_stamp = 0;

            ///Where is the event coming from?
            EventManager::event_type_e type = event_type_e::NONE;

            ///The actual event information, just cast it to the appropriate data type.
            ///@remark Not all event types will have data with more specific information about it.
            void *p_data = nullptr;

            ///Data provided by the listener to be sent with the event occurrence.
            void *p_user_package = nullptr;
        };//end event

        /** @brief Function signature for listeners event callback. */
        typedef void (*EventListenerCallback) (void *obj_this, const EventManager::event_s &ev);


    private:
        //return a unique identification for every new listener
        std::atomic<listenerID_t> last_generated_id;

        std::mutex _message_queue_semaphore; //Coordinates SDL's event filter with our event queue.

        //register listeners for input events
        struct listener_s
        {
            listenerID_t ID = 0;
            //OR value containing the event types this listener is interested.
            //makes sorting much faster!
            EventManager::event_type_e u_event_family_filter = (event_type_e)event_type_e::NONE;

            //used for real time event handling
            EventManager::EventListenerCallback func_process_events_callback = nullptr; //may be null
            void *p_cb_package = nullptr; //may be null

            //I don't like having to use std::vector here, but building a reliable resizable array is hard!
            std::vector<EventManager::event_s *> v_listen_for;
            FIFO<EventManager::event_s *> ff_events_queue; //just pointers to the actual events.

            /** @brief Compares two listeners.
             * @return <b>true</b> if the two have the same id; <b>false</b> if different. */
            bool operator== (const listener_s &other) const {return (this->ID == other.ID);}
        };//end listener

        LinkedList<EventManager::listener_s> ll_listeners;
        //Most likely, listeners are going in order reading their queues one at a time till empty, so we save the last one.
        EventManager::listener_s *p_last_listener = nullptr;


        //List of interfaces to fetch events
        Keyboard interface_keyboard;

        //hide constructor to make singleton safe
        EventManager ();

        //this is necessary to manage the linked list for listeners.
        void removeListener (LinkedList<EventManager::listener_s>::iterator *it);
        static void _clearListenerStruct (listener_s *ls);
        LinkedList<EventManager::listener_s>::iterator _searchListenerByID (listenerID_t id);

        //this is necessary since we have pointers inside the structure with dynamic allocation
        //   and using a constructor/destructor in the struct fucks with the event queue passing events around.
        static void fillEventStructure (EventManager::event_s &ev, EventManager::event_type_e ev_type, void *ev_info, void *user_package);
        static void clearEventStructure (EventManager::event_s &ev);

        //to receive SDL events as they happen and skip the queue!
        static int _SDL_eventFilter_cb (void *event_manager_i, SDL_Event *event);

        const EventManager::event_s * listenForEvent (listenerID_t id, EventManager::event_type_e ev_type, void *ev_info, void *user_package = nullptr);

    protected:


    public:
        static EventManager& instance ();
        ~EventManager ();

        //delete copy and move constructors and assign operators to make singleton safe
        EventManager (EventManager const&) = delete;            //Copy construct
        EventManager (EventManager&&) = delete;                 //Move construct
        EventManager& operator= (EventManager const&) = delete; //Copy assign
        EventManager& operator= (EventManager &&) = delete;     //Move assign

        bool waitKeyUpEvent (EventManager::EventListenerCallback cb, void *user_package = nullptr);

        listenerID_t addListener (EventManager::EventListenerCallback cb = nullptr, void *user_package = nullptr);
        void removeListener (listenerID_t id);

        void update ();
        const EventManager::event_s* pop (listenerID_t id);
        void push (game_event_id_e g_ev_id, const glm::vec3 &pos);
        void push (void *ev_data, void *user_data = nullptr);

        const EventManager::event_s * listenForQuitEvent (listenerID_t id, void *user_package = nullptr);

        const EventManager::event_s * listenForEvent (listenerID_t id, SDL_WindowEventID win_ev_id, void *user_package = nullptr);
        const EventManager::event_s * listenForEvent (listenerID_t id, key_state key_st, SDL_Scancode sc, SDL_Keymod mod = KMOD_NONE, void *user_package = nullptr);
        const EventManager::event_s * listenForEvent (listenerID_t id, game_event_id_e g_ev_id, void *user_package = nullptr);

        const EventManager::event_s * listenForUserEvents (listenerID_t id, void *user_package = nullptr);

        static bool isEqual (const EventManager::event_s &ev1, const EventManager::event_s &ev2);
};//END EventManager

#endif //_EVENT_MANAGER_HPP_

